﻿using myDocumentClustering.DocumentTermMatrix;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace myDocumentClustering.SAM
{
    public class SAMWorker
    {
        public Documents docs;
        public DTM dtMatrix;
        public List<HACCluster> gen1Clusters;
        private List<HACCluster> gen2Clusters;
        private Int32 expectedClusterNumber;

        public SAMWorker(Documents docs, DTM dtMatrix, Int32 expectedClusterNumber)
        {
            this.docs = docs;
            this.dtMatrix = dtMatrix;
            this.expectedClusterNumber = expectedClusterNumber;
        }

        /// <summary>
        /// Distribute all document vectors randomly into expected number of clusters.
        /// We must make sure each cluster has at least 1 document.
        /// What if document number is LESS than cluster number.
        /// </summary>
        /// <param name="expectedClusterNumber"></param>
        public void InitializeRandomClustering(Int32 expectedClusterNumber)
        {
            gen1Clusters = new List<HACCluster>();
            Random rand = new Random(DateTime.Now.Second);

            for (Int32 i = 0; i < expectedClusterNumber; i++) //cluster index is defined as [0, expectedClusternumber-1]
            {
                gen1Clusters.Add(new HACCluster());
            }

            Dictionary<Int32, Int32> clusterEmptyMap = new Dictionary<int, int>();

            foreach (var docIndex in docs.DocIndexes)
            {
                Int32 randomClusterIndex = rand.Next(expectedClusterNumber);
                clusterEmptyMap[randomClusterIndex] = 1;
                HACCluster cluster = gen1Clusters[randomClusterIndex];
                DTV docVector = dtMatrix[docIndex];
                cluster.AddVector(docVector);
            }
        }

        /// <summary>
        /// split all the clusters.
        /// </summary>
        public void SplitGen1ToGen2(Boolean outputSplitResult)
        {
            gen2Clusters = new List<HACCluster>();
            foreach(var cluster in gen1Clusters)
            {
                List<HACCluster> splitClusters = cluster.Polarize();
                gen2Clusters.AddRange(splitClusters);

                if (outputSplitResult)
                {
                    Console.WriteLine("Polarization result START");
                    foreach (var c in splitClusters)
                    {
                        Console.WriteLine("\t-----------");
                        OutputCluster(c);
                        Console.WriteLine("\t-----------");
                    }
                    Console.WriteLine("Polarization result END");
                }
            }

            Int32 gen2TotalDocs = 0;
            foreach(var c in gen2Clusters)
            {
                gen2TotalDocs += c.vectors.Count;
            }
            Console.WriteLine("\ngen2TotalDocs:{0}", gen2TotalDocs);
            Console.WriteLine("gen2Cluster#: {0}", gen2Clusters.Count);
        }

        public void MergeGen2toGen1()
        {
            //gen1Clusters = new List<HACCluster>();

            Int32 maxDimension = 2 * expectedClusterNumber; // the matrix dimension will not exceeed 2 * expctedClusterNumber
            float[,] clusterSimilarityMatrix = new float[maxDimension, maxDimension];

            while (gen2Clusters.Count > gen1Clusters.Count)
            {
                //calculate the cluster similarity matrix and locate the 2 closet clusters.
                float maxSimilarity = float.MinValue;
                Int32 iCloset = 0;
                Int32 jCloset = 0;
                for (Int32 i = 0; i < gen2Clusters.Count - 1; i++)
                    for (Int32 j = i + 1; j < gen2Clusters.Count; j++)
                    {
                        float similarity = HACCluster.GetClustersSimilarity(gen2Clusters[i], gen2Clusters[j]);
                        if (float.IsNaN(similarity))
                            throw new NotFiniteNumberException();

                        if(similarity>maxSimilarity)
                        {
                            iCloset = i;
                            jCloset = j;
                            maxSimilarity = similarity;
                        }
                        clusterSimilarityMatrix[i, j] = similarity;
                    }

                //merge the 2 closet clusters to 1
                HACCluster clusteri = gen2Clusters[iCloset];
                HACCluster clusterj = gen2Clusters[jCloset];
                clusteri += clusterj; //the operator + must NOT return the reference to clusterj, because clusterj will be removed.

                //remove the 1 leftover cluster
                gen2Clusters.RemoveAt(jCloset);
            }

            
            //move gen2 merged clusters to gen1
            gen1Clusters = gen2Clusters;
        }

        public void OutputResult()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Int32 i=0;
            Int32 totalDocs = 0;
            foreach(var cluster in gen1Clusters)
            {
                Console.WriteLine("----Cluster {0}---- Count:{1}", i++, cluster.vectors.Count);
                totalDocs += cluster.vectors.Count;
                OutputCluster(cluster);
            }
            Console.WriteLine("Total docs count = {0}", totalDocs);
            Console.ResetColor();
        }

        public void OutputCluster(HACCluster cluster)
        {
            Console.WriteLine("Cluster size: {0}", cluster.vectors.Count);
            foreach (var doc in cluster.vectors)
            {
                Console.WriteLine(docs.DocNames[doc.docIndex]);
            }

        }
    }
}
